use std::fmt::Debug;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::path::Path;
use std::io::BufReader;
use std::io::BufWriter;
use std::io::ErrorKind;
use std::marker::PhantomData;

use crate::constants::BACKUP_CHUNK_POINTER_RECIPE_LOG;
use crate::constants::RESTORE_CHUNK_POINTER_RECIPE_LOG;

use super::RecipeError;
use super::RecipeResult;
//use super::pointer::ChunkPointer;
use super::constants::SEG_RECORD_BUF_LEN;
use super::pointer::ChunkPointer;

pub struct SegRecipeWriter<T:ChunkPointer+Debug> {
	pad:PhantomData<T>,
	output:BufWriter<File>,
}

impl<T:ChunkPointer+Debug> SegRecipeWriter<T> {
	pub fn from_path<P:AsRef<Path>+?Sized>(path:&P) -> RecipeResult<Self> {
		Ok(Self {
			pad:PhantomData::<T>::default(),
			output:BufWriter::with_capacity(
				SEG_RECORD_BUF_LEN,
				File::create(path).map_err(|e|RecipeError::SegWriterOpen(e))?,
			),
		})
	}
	
	pub fn from_file(f:File) -> Self {
		Self {
			pad:PhantomData::<T>::default(),
			output:BufWriter::with_capacity(SEG_RECORD_BUF_LEN,f),
		}
	}

	pub fn cursor(&mut self) -> std::io::Result<u64> {
		self.output.stream_position()
	}
	
	pub fn append(&mut self,p:&T) -> RecipeResult<()> {
		log::debug!(target:BACKUP_CHUNK_POINTER_RECIPE_LOG,"chunk pointer recipe is written with {:?} at {}\n",p,self.cursor().unwrap());
		self.output.write(p.as_raw_data()).map_err(|e|RecipeError::AppendPointer(e))?;
		Ok(())
	}
}

pub struct SegRecipeReader<T:ChunkPointer+Debug> {
	pad:PhantomData<T>,
	input:BufReader<File>,
}

impl<T:ChunkPointer+Debug> SegRecipeReader<T> {
	pub fn from_path<P:AsRef<Path>+?Sized>(path:&P) -> RecipeResult<Self> {
		Ok(Self {
			pad:PhantomData::<T>::default(),
			input:BufReader::with_capacity(
				SEG_RECORD_BUF_LEN,
				File::open(path).map_err(|e|RecipeError::SegReaderOpen(e))?,
			),
		})
	}
	
	pub fn from_file(f:File) -> Self {
		Self {
			pad:PhantomData::<T>::default(),
			input:BufReader::with_capacity(SEG_RECORD_BUF_LEN,f),
		}
	}

	pub fn cursor(&mut self) -> std::io::Result<u64> {
		self.input.stream_position()
	}
	
	pub fn get(&mut self) -> RecipeResult<Option<Box<T>>> {
		let read_position = self.cursor().unwrap();
		let mut p = Box::<T>::new_uninit();
		let read_p = unsafe { p.as_mut_ptr().as_mut().expect("REASON") };
		match self.input.read_exact(read_p.as_raw_mut()) {
			Ok(()) => unsafe { 
				let p = p.assume_init();
				log::debug!(target:RESTORE_CHUNK_POINTER_RECIPE_LOG,"chunk pointer recipe is read with {:?} at {}\n",p,read_position);
				Ok(Some(p)) 
			}
			Err(e) => {
				match e.kind() {
					ErrorKind::UnexpectedEof => { Ok(None) }
					_ => { Err(RecipeError::GetPointer(e)) } 
				}
			}
		}
	}
}
